﻿using System;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.WowAddonStudio.FrameXml.Components.Support;
using Microsoft.WowAddonStudio.FrameXml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Services;

namespace Microsoft.WowAddonStudio.FrameXml.Components
{
	[ToolboxBitmap(typeof (Label), "Label.bmp")]
	[ToolboxItemFilter("WowAddonStudio", ToolboxItemFilterType.Require)]
    public partial class FrameXmlComponent<TSerializationObject> : Component, IFrameXmlComponent 
		where TSerializationObject : SerializationObject, new()
	{
        private SerializationObject serializationObject;
		private const string parentNameToken = "$parent";

        /// <summary>
        /// Initializes a new instance of the <see cref="FrameXmlComponent&lt;TSerializationObject&gt;"/> class.
        /// </summary>
		public FrameXmlComponent()
		{
            InitializeFrameXmlComponent(null);
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="FrameXmlComponent&lt;TSerializationObject&gt;"/> class.
        /// </summary>
        /// <param name="serializationObject">The serialization object.</param>
		public FrameXmlComponent(TSerializationObject serializationObject)
		{
            if (serializationObject == null)
                throw new ArgumentNullException("serializationObject");

            InitializeFrameXmlComponent(serializationObject);
		}

        /// <summary>
        /// Initializes the frame XML component.
        /// </summary>
        /// <param name="serializationObject">The serialization object.</param>
        private void InitializeFrameXmlComponent(TSerializationObject serializationObject)
        {
            this.SerializationObject = serializationObject ?? SerializationObjectFactory.Default.CreateSerializationObject(this.SerializationObjectType);
        }

	    /// <summary>
        /// Initializes this instance.
        /// </summary>
        protected virtual void Initialize()
        {
        }

	    /// <summary>
		/// Gets the serialization object.
		/// </summary>
		//public SerializationObject SerializationObject { get; set; }
        public virtual SerializationObject SerializationObject
        {
            get { return serializationObject; }
            set
            {
                if (serializationObject != value)
                {
                    var notifyingObject = serializationObject as INotifyPropertyChanged;
                    if (notifyingObject != null)
                    {
                        notifyingObject.PropertyChanged -= OnPropertyChanged;
                    }

                    serializationObject = value;

                    notifyingObject = serializationObject as INotifyPropertyChanged;
                    if (notifyingObject != null)
                    {
                        notifyingObject.PropertyChanged += OnPropertyChanged;
                    }
                }
            }
        }

		/// <summary>
		/// Gets the parent FrameXML component.
		/// </summary>
		IFrameXmlComponent IFrameXmlComponent.Parent{ get; set; }

        /// <summary>
        /// Gets or sets the <see cref="T:System.ComponentModel.ISite"/> of the <see cref="T:System.ComponentModel.Component"/>.
        /// </summary>
        /// <value></value>
        /// <returns>The <see cref="T:System.ComponentModel.ISite"/> associated with the <see cref="T:System.ComponentModel.Component"/>, or null if the <see cref="T:System.ComponentModel.Component"/> is not encapsulated in an <see cref="T:System.ComponentModel.IContainer"/>, the <see cref="T:System.ComponentModel.Component"/> does not have an <see cref="T:System.ComponentModel.ISite"/> associated with it, or the <see cref="T:System.ComponentModel.Component"/> is removed from its <see cref="T:System.ComponentModel.IContainer"/>.</returns>
        public override ISite Site
        {
            set
            {
                base.Site = value;
                if (Site != null)
                {
                    this.Initialize();
                    Name = Site.Name;
                }
            }
        }

		/// <summary>
		/// Gets or sets the name of the FrameXML component.
		/// </summary>
		public string Name
		{
			get { return Site != null ? Site.Name : null; }
			set
			{
				if (Site == null)
					throw new InvalidOperationException("The component is not sited.");

                Site.Name = value;
			}
		}

		/// <summary>
		/// Expands a name by replacing the parent token with the name of the layout frame.
		/// </summary>
		/// <param name="name">The name to expand.</param>
		/// <returns>The expanded name.</returns>
		public string ExpandName(string name)
		{
			if (name == null)
				throw new ArgumentNullException("name");

			return name.Replace(parentNameToken, Name);
		}

		/// <summary>
		/// Expands the name of a named object.
		/// </summary>
		/// <param name="namedObject">The named object.</param>
		/// <returns>The expanded name.</returns>
		public string ExpandName(INamedObject namedObject)
		{
			if (namedObject == null)
				throw new ArgumentNullException("namedObject");
			if (namedObject.Name == null)
				throw new ArgumentException("Named object does not have a name.", "namedObject");

			return namedObject.Name != null ? ExpandName(namedObject.Name) : null;
		}

        /// <summary>
        /// Called when [property changed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "Name":
                    InitializeName();
                    break;
                case "Inherits":
                    RefreshPropertyBrowser();
                    break;
            }

            SetDirtyFlag();
        }

	    /// <summary>
        /// Gets the type of the serialization object this control manages.
        /// </summary>
        public virtual Type SerializationObjectType
        {
            get { return typeof(TSerializationObject); }
        }

        /// <summary>
        /// Gets the inheritable object.
        /// </summary>
        /// <value>The inheritable object.</value>
	    public InheritableObject InheritableObject
	    {
            get { return this.serializationObject as InheritableObject; }
	    }

	    /// <summary>
        /// Sets the dirty flag.
        /// </summary>
        private void SetDirtyFlag()
        {
            var framXmlDesignService = ((IFrameXmlDesignerService)this.GetService(typeof(IFrameXmlDesignerService)));

            if (framXmlDesignService != null)
                framXmlDesignService.SetDirty(true);
        }

        /// <summary>
        /// Initializes the name.
        /// </summary>
	    public void InitializeName()
	    {
            var expandedName = this.InheritableObject.Name;

            //Get INameCreationService implementation
            var nameCreationService = GetService(typeof(INameCreationService)) as IFrameXmlNameCreationService;

            //Get the current container containing the control
            var container = GetService(typeof(IContainer)) as IContainer;

            if (nameCreationService != null)
            {
                try
                {
                    //Check that given name is valid
                    if (!nameCreationService.HasValidName(this, expandedName))
                        Site.Name = Name = InheritableObject.Name = nameCreationService.CreateName(container, expandedName);
                    else
                        Site.Name = Name = InheritableObject.Name = expandedName;
                }
                catch (Exception e)
                {
                    Trace.WriteLine(e);
                    // Generate a unique name for component
                    Site.Name =
                        Name =
                        nameCreationService.CreateName(container,
                                                       String.Format(CultureInfo.InvariantCulture, "{0}_{1:B}", expandedName,
                                                                     Guid.NewGuid()));
                }
            }
	    }

        /// <summary>
        /// Refreshes the property browser.
        /// </summary>
        protected void RefreshPropertyBrowser()
        {
            var shell = GetService(typeof(SVsUIShell)) as IVsUIShell;
            if (shell != null)
            {
                shell.RefreshPropertyBrowser(0);
            }
        }
	}
}